package com.blz.flare.solr;

import com.blz.flare.crawler.PostException;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.IOException;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.Map;

/**
 * A utility for adding/updating a webpage's indexes.
 * 
 * @author pchapman
 * @author jeremy.arnold
 *
 * // TODO: replace this with embedded solr component? {@link org.apache.solr.client.solrj.embedded.EmbeddedSolrServer}
 *
 */
public class SolrUpdaterTool
{
	private static final String SOLR_OK_RESPONSE_EXCERPT = "<int name=\"status\">0</int>";
	private static final Logger logger = LoggerFactory.getLogger(SolrUpdaterTool.class);

	private URL updateURL;

    /**
     * Construct a new {@link SolrUpdaterTool} with a given solr url.
     * @param solrURL the solr url
     * @throws MalformedURLException on error
     */
	public SolrUpdaterTool(URL solrURL)
		    throws MalformedURLException {
		this.updateURL = new URL(solrURL.toString() + "/update");
	}

    /**
     * @return an {@link HttpClient} used to interact with Solr instance.
     */
    public HttpClient getHttpClient() {
        HostConfiguration conf = new HostConfiguration();
        conf.setHost(updateURL.getHost(), updateURL.getPort());
        HttpClient httpclient = new HttpClient();
        httpclient.setHostConfiguration(conf);
        logger.info("Host: {}  Port: {}", conf.getHost(), conf.getPort());
        return httpclient;
    }

    /**
     * Update solr indexes.
     *
     * @param url the url
     * @param title the title
     * @param text the text
     * @param indexedFields the fields to index
     * @throws com.blz.flare.crawler.PostException on error
     */
	public void updateIndexes(URI url, String title,
            String text, Map<String, String> indexedFields)
            throws PostException {
        
		logger.debug("Publishing indexes for URI {}", url);
		Document doc;
		Element docElem;
		Element fieldElem;
		Element rootElem;
		
		try {
			// Initialization
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder;
			builder = factory.newDocumentBuilder();

			// XML to update indexes
			doc = builder.newDocument();
			rootElem = doc.createElement("add");
			doc.appendChild(rootElem);
			docElem = doc.createElement("doc");
			rootElem.appendChild(docElem);
			fieldElem = doc.createElement("field");
			fieldElem.setAttribute("name", "id");
			fieldElem.appendChild(doc.createTextNode(url.toString()));
			docElem.appendChild(fieldElem);
			fieldElem = doc.createElement("field");
			fieldElem.setAttribute("name", "title");
			fieldElem.appendChild(doc.createTextNode(title));
			docElem.appendChild(fieldElem);
			fieldElem = doc.createElement("field");
			fieldElem.setAttribute("name", "text");
			fieldElem.appendChild(doc.createTextNode(text));
			docElem.appendChild(fieldElem);
			String value;

            // add all the index fields
			for (String field : indexedFields.keySet()) {
				value = indexedFields.get(field);
				if (value != null && value.length() > 0) {
					fieldElem = doc.createElement("field");
					fieldElem.setAttribute("name", field);
					fieldElem.appendChild(doc.createTextNode(value));
					docElem.appendChild(fieldElem);
				}
			}

            // post to solr
			sendPost(doc);
	
			// XML to commit
			doc = builder.newDocument();
			rootElem = doc.createElement("commit");
			doc.appendChild(rootElem);

			sendPost(doc);
		} catch (ParserConfigurationException e) {
			throw new PostException("Unable to build XML", e);
		}
	}

    /**
     * Send a post to Solr.
     * @param doc the {@link Document}
     * @throws PostException
     */
	private void sendPost(Document doc)
            throws PostException {
        
		int resp;
		String responsetext;

		try {
			// Initialization
			TransformerFactory tFactory = TransformerFactory.newInstance();
			Transformer transformer = tFactory.newTransformer();

			// build the xml to post
			StringWriter xmlstr = new StringWriter();
			StreamResult result = new StreamResult(xmlstr);
			DOMSource source = new DOMSource(doc);
			transformer.transform(source, result);

            xmlstr.close();

            // post the xml
            HttpMethod method = new PostMethod();
            method.setPath(updateURL.toString());
            ((PostMethod)method).setRequestEntity(new StringRequestEntity(xmlstr.toString(), "text/xml", "UTF-8"));
            resp = getHttpClient().executeMethod(method);
            responsetext = method.getResponseBodyAsString();

			// Test the response
            if (resp == 200) {
    			if (responsetext.indexOf(SOLR_OK_RESPONSE_EXCERPT) < 0) {
    				throw new PostException("Unexpected SOLR response " + responsetext);
    			}
            } else {
            	logger.error("Error uploading image data.  Error {}: {}", resp, responsetext);
            	throw new PostException("Error uploading image data.  Error " + resp + ": " + responsetext);
            }
		} catch (IOException e) {
			throw new PostException("Unable to post XMl or read response", e);
		} catch (TransformerException e) {
			throw new PostException("Unable to transform XML", e);
		}
	}
}
